/**
 * Copyright (c) 2006, Reliable Source, Inc. All Rights Reserved.
 */
package org.reliablesource.prism.ui.workbench.views;

import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.part.ViewPart;
import org.reliablesource.prism.core.IPrismScreen;
import org.reliablesource.prism.core.IPrismTutorial;
import org.reliablesource.prism.core.plugin.PrismCorePlugin;
import org.reliablesource.prism.ui.plugin.PrismPlugin;

/**
 * AnimationViewPart.java<br> 
 * Created on: May 10, 2006<p>
 * 
 * @author Dennis Park 
 * 	<a href="mailto:dennis.park@gmail.com">dennis.park@gmail.com</a>
 * 
 *  @since 1.0
 *  
 */

public class OrigAnimationView extends ViewPart implements ISelectionListener {
	public final static String VIEW_ID = "org.reliablesource.prism.ui.views.animationview";

	final static int DELAY = 18;

	final static int PAUSE = 1800;
	
	final static int X_OFFSET = -4;
	final static int Y_OFFSET = -1;

	final private Display display;

	final private Image pointer;

	final private AudioClip clickSound;

	private volatile int imageDataIndex;

	private List selections;

	private GC offScreenImageGC;

	private volatile Image offScreenImage;

	private int xa, ya, xb, yb;

	private int drawX, drawY;

	private ImageData[] imageDataArray;

	private Canvas animationCanvas;

	private Thread animateThread;

	private IPrismTutorial tutorial;

	private volatile IPrismScreen currentScreen;

	private volatile List polyLinePath;

	private ImageData imageData;

	private ToolBar toolBar;

	private volatile ToolItem play;

	private volatile ToolItem stop;

	private volatile ToolItem reload;

	private volatile ToolItem forward;

	private volatile ToolItem backward;

	private volatile ToolItem pause;

	private volatile static Boolean paused;

	private volatile boolean indexChanged;

	private volatile boolean playAll;

	public OrigAnimationView() {
		super();

		display = Display.getDefault();
		playAll = false;
		playAll = true;
				
		paused = Boolean.FALSE;
		indexChanged = true;

		clickSound = Applet.newAudioClip(PrismPlugin.getDefault().getBundle().getResource("sounds/CLICKERX.WAV"));
		pointer = PrismPlugin.getImageDescriptor("icons/pointer.gif").createImage();
	}

	public void createPartControl(final Composite parent) {

		Composite topComposite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		topComposite.setLayout(layout);

		// Listen only to selection in the Navigator.
		getSite().getPage().addSelectionListener(NavigatorViewPart.VIEW_ID,
				this);

		createToolbar(topComposite);
		animationCanvas = new Canvas(topComposite, SWT.DOUBLE_BUFFERED);
		
		String contextKey  = VIEW_ID + "." +  "AnimationCanvas";
		PrismPlugin.getDefault().addWidget(contextKey, animationCanvas);
		
		animationCanvas.setLayoutData(new GridData(GridData.FILL_BOTH));

		animationCanvas.addPaintListener(new PaintListener() {
			public void paintControl(final PaintEvent pe) {
				if (offScreenImage != null && !offScreenImage.isDisposed()) {

//					if (indexChanged)
//						return;

					pe.gc.drawImage(offScreenImage, 0, 0);

					if (indexChanged)
						return;
					pe.gc.drawImage(pointer, drawX, drawY);

					// Debugging pointer guidance logic
					if (polyLinePath != null) {
						Iterator it = polyLinePath.iterator();
						if (it.hasNext()) {
							Point pt = (Point) it.next();
							Point pt2;

							while (it.hasNext()) {
								pt2 = (Point) it.next();
								if (indexChanged)
									return;
								pe.gc.drawLine(pt.x, pt.y, pt2.x, pt2.y);
								pt = pt2;
							}
						}
					} else { // not a polyLine path														
						int startX = currentScreen.getPointerStart().x;
						int startY = currentScreen.getPointerStart().y;
						int endX = currentScreen.getPointerEnd().x;
						int endY = currentScreen.getPointerEnd().y;

						if (indexChanged)
							return;

						pe.gc.drawLine(startX, startY, endX, endY);
					}
					// Debug statements
					// e.gc.drawText("Image #: "
					// + imageDataIndex + ": " + x + "-"
					// + y + "@@" + x2 + "-" + y2, 350,
					// 550);
				}
			}
		});

		PrismPlugin.getDefault().addWidget(VIEW_ID, parent);
		PrismPlugin.getDefault().dumpCompositeContextMap();
	}

	public void startAnimation() {

		// Load full set of screens - Maybe implement pagination?
		if (tutorial == null)
			tutorial = ((IPrismTutorial) PrismCorePlugin.getDefault().getModel().getChild(0));

		tutorial = ((IPrismTutorial) PrismCorePlugin.getDefault().getModel().getChild(0));
		int nScreens = tutorial.getChildListSize();
		imageDataArray = new ImageData[nScreens];

		for (int i = 0; i < nScreens; i++) {
			IPrismScreen screen = (IPrismScreen) tutorial.getChild(i);
			imageDataArray[i] = PrismPlugin.getDefault().readScreen(screen)
					.getImageData();
		}

		xa = 0;
		ya = 0;
		xb = 0;
		yb = 0;

		try {
			if (imageDataArray.length > 0) {

				animateThread = new Thread("Animation") {
					public void run() {

						Image image = null;
						// Create an off-screen image to draw on, and fill it
						// with the shell background.
						offScreenImage = new Image(display,
								imageDataArray[0].width,
								imageDataArray[0].height);
						offScreenImageGC = new GC(offScreenImage);
						imageData = imageDataArray[0];
						offScreenImageGC.fillRectangle(0, 0, imageData.width,
								imageData.height);

						try {
							Thread thisThread = Thread.currentThread();
							while (this == thisThread) {
								if (indexChanged || playAll) {
									// Handling users' selection

									// reset the imgDataIdx
									//	if(!playAll)
									//		imageDataIndex = 0;

									// User selected more than one item.
									if (selections != null)
										//	Get screen from selection list
										currentScreen = (IPrismScreen) selections
												.get(imageDataIndex);

									//	Check to see if have a reference to 
									//		currentScreen.  Should pass this
									//		condition if imageDataIdx is valid.
									if (currentScreen == null)
										currentScreen = (IPrismScreen) tutorial
												.getChild(imageDataIndex);
									else
										imageDataIndex = tutorial
												.getChildSequence(currentScreen);

									//	Create the first image, draw it on the
									// 		off-screen image.
									imageData = imageDataArray[imageDataIndex];
									if (image != null && !image.isDisposed())
										image.dispose();
									image = new Image(display, imageData);

									offScreenImageGC.drawImage(image, 0, 0,
											imageData.width, imageData.height,
											imageData.x, imageData.y,
											imageData.width, imageData.height);

									//	Loop through the imgs, creating &
									// 	 	drawing each one on the off-screen 
									//		img before drawing it on the shell.

									display.syncExec(new Runnable() {
										public void run() {
											indexChanged = false;
											animationCanvas.redraw();
										}
									});
								}

								/* Determine the start point, end point, and
								 *	intermediate points.
								 */

								//	Start pt
								xa = currentScreen.getPointerStart().x;
								ya = currentScreen.getPointerStart().y;
								polyLinePath = new ArrayList();

								//	Immediately add the first to path list.
								polyLinePath.add(currentScreen
										.getPointerStart());

//								int listSize = currentScreen
//										.getPathLocationsList().size();
								
								
								//	FIXME:	Should catch an NPE and array out of
								//			boucnds exception.  There are 
								//				use cases when this can happen.
								//				(ie. undo/redo)
								
								//	NOTE:	We want the animation behavior to
								//				be able to modify its' path in
								//				real time response to the user
								//				editing the path, so do not
								//				pre-calculate the size of the
								//				path list.																
								if (currentScreen.getPathLocationsList().size() > 0) {
									for (int i = 0; i < currentScreen
									.getPathLocationsList().size(); i++) {
										if (indexChanged)
											break;
										Point bendPoint = currentScreen
												.getPathLocation(i);
										xb = bendPoint.x;
										yb = bendPoint.y;

										if (!((xa == xb) && (ya == yb))) {
											polyLinePath.add(bendPoint);
											drawLineBresenham(xa, ya, xb, yb);
											xa = xb;
											ya = yb;
										}
									}
								}

								xb = currentScreen.getPointerEnd().x;
								yb = currentScreen.getPointerEnd().y;

								// Bresenham's Line Drawing Algorithm
								if ((!((xa == xb) && (ya == yb)))) {
									polyLinePath.add(currentScreen
											.getPointerEnd());

									if (!indexChanged)
										drawLineBresenham(xa, ya, xb, yb);
									else
										polyLinePath.clear();
								}

								//imageDataIndex++;

								if (playAll) {
									// If we have just drawn the last image, repeat.
									if (imageDataIndex == (imageDataArray.length - 1))
										imageDataIndex = 0;
									else
										//	Is this supposed to happen *before* 
										//		the above condition is checked?
										imageDataIndex++;

								}
							}
						} catch (final SWTException ex) {
							System.out
									.println("There was an error animating the screens.");
							System.err.println(ex);
						} finally {
							if (animateThread != null) {
								animateThread.interrupt();
								animateThread = null;
							}
							if (offScreenImage != null
									&& !offScreenImage.isDisposed()) 
								offScreenImage.dispose();
							if (image != null && !image.isDisposed()) 
								image.dispose();
							if (pointer != null && !pointer.isDisposed()) 
								pointer.dispose();
						}
					}
				};
				animateThread.start();
			}
		} catch (final Exception ex) {
			System.out.println("There was an error loading the GIF");
		}
	}

	public void setFocus() {
	}

	public void selectionChanged(final IWorkbenchPart part,
			final ISelection selection) {

		StructuredSelection changedSelectionTo = (StructuredSelection) selection;
		if (changedSelectionTo.getFirstElement().equals(currentScreen))
			return;
		int nItems = changedSelectionTo.size();

		if (nItems > 1) {
			selections = new ArrayList();
			Iterator it = changedSelectionTo.iterator();
			while (it.hasNext()) {
				try {
					selections.add(it.next());
					indexChanged = true;
				} catch (final ClassCastException cce) {
					System.err.println(cce);
				}
			}

		} else if (changedSelectionTo.getFirstElement() instanceof IPrismTutorial) {
			indexChanged = true;
			tutorial = (IPrismTutorial) changedSelectionTo.getFirstElement();
		} else if (changedSelectionTo.getFirstElement() instanceof IPrismScreen) {
			indexChanged = true;
			currentScreen = (IPrismScreen) changedSelectionTo.getFirstElement();

		}
	}

	public void drawLineBresenham(int x_1, int y_1, int x_2, int y_2) {
		double deltax, deltay;
		double slope;

		deltax = x_2 - x_1; // Change in x
		deltay = y_2 - y_1; // Change in y

		// Check for divide by zero
		if (deltax == 0)
			linePosSteep(x_1, y_1, x_2, y_2);
		else {
			slope = deltay / deltax;

			if (slope > 0.0) {
				if (slope > 1.0)
					linePosSteep(x_1, y_1, x_2, y_2);
				else
					linePosShallow(x_1, y_1, x_2, y_2);
			} else {
				if (slope > -1.0) {
					lineNegShallow(x_1, y_1, x_2, y_2);
				} else {
					lineNegSteep(x_1, y_1, x_2, y_2);
				}
			}
		}
	}

	public void linePosShallow(int x_1, int y_1, int x_2, int y_2) {
		int dx, dy, p, twody, twodx, twodydx, twodxdy, x, y, x2, y2, xend;

		dx = Math.abs(x_1 - x_2);
		dy = Math.abs(y_1 - y_2);
		twodxdy = 2 * (dx - dy);
		twody = 2 * dy;
		twodydx = 2 * (dy - dx);
		twodx = 2 * dx;
		// NOTE: both expressions for 'p' seem to work on the test case.
		// p = twodx - dy;
		p = twody - dx;

		x = x_1;
		y = y_1;

		x2 = x_2;
		y2 = y_2;

		xend = x2;

		drawPixel(x, y, x2, y2);

		if (indexChanged)
			return;

		// ------------Capture interlude----------------
		if (paused.equals(Boolean.FALSE))
			clickSound.play();
		try {
			Thread.sleep(PAUSE);
		} catch (InterruptedException e) {
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}
		// ---------------------------------------------

		if (x < xend) {
			while (x < xend && !(indexChanged)) {
				x++;
				if (p < 0)
					p += twody;
				else {
					y++;
					p += twodydx;
				}
				drawPixel(x, y, x2, y2);
			}
		}

		/**
		 * Here we are going from a higher x1, to a lower x value, and then a
		 * lower y value to a higher one.
		 * 
		 * x1 > x2 && y1 > y2
		 * 
		 */
		else {
//			p = twodx - dy;
			while (x > xend && !(indexChanged)) {
				x--;
				if (p < 0)
					p += twody;
				else {
					y--;
					p += twodydx;
				}
				drawPixel(x, y, x2, y2);
			}
		}
	}

	public void linePosSteep(int x_1, int y_1, int x_2, int y_2) {
		int dx, dy, p, twodx, twody, twodydx,twodxdy, x, y, x2, y2, yend;

		dx = Math.abs(x_1 - x_2);
		dy = Math.abs(y_1 - y_2);

		twodx = 2 * dx;
		twody = 2 * dy;
		p = twodx - dy;
//		p = twody - dx;
		
		twodxdy = 2 * (dx - dy);
		twodydx = 2 * (dy - dx);
		

		y = y_1;
		yend = y_2;
		x = x_1;
		x2 = x_2;
		y2 = y_2;

		// Vertical line
		if (x_1 == x_2) {
			if (y_2 > y_1) {
				yend = y_2;
				y = y_1;
				y2 = y_2;
				x2 = x_2;
			} else {
				yend = y_1;
				y = y_2;
			}
			for (; y <= yend; y++)
				drawPixel(x_1, y, x2, y2);
		}

		x = x_1;
		y = y_1;
		yend = y_2;

		y2 = y_2;
		x2 = x_2;

		if (x_1 != x_2) {
			drawPixel(x_1, y_1, x2, y2);

			if (indexChanged)
				return;
			// ------------Capture interlude----------------
			if (paused.equals(Boolean.FALSE))
				clickSound.play();
			try {
				Thread.sleep(PAUSE);
			} catch (InterruptedException e) {
				e.printStackTrace();
				Thread.currentThread().interrupt();
			}
			// ---------------------------------------------
			if (y < yend) {
				while (y < yend && !(indexChanged)) {
					y++;
					if (p < 0)
						p += twodx;
					else {
						x++;
						p += twodxdy;
					}
					drawPixel(x, y, x2, y2);
				}
			} else {
				while (y > yend && !(indexChanged)) {
					y--;
					if (p < 0)
						p += twodx;
					
					else {
						x--;
						p += twodxdy;
					}
					drawPixel(x, y, x2, y2);
				}

			}
		}
	}

	public void lineNegShallow(int x_1, int y_1, int x_2, int y_2) {
		int dx, dy, p, twody, twodydx, x, y, x2, y2, xend;

		dx = Math.abs(x_2 - x_1);
		dy = Math.abs(y_2 - y_1);
		p = 2 * dy - dx;
		twody = 2 * dy;
		twodydx = 2 * (dy - dx);
		x = x_1;
		y = y_1;
		x2 = x_2;
		y2 = y_2;
		xend = x2;

		int yend = y_2;

		x = x_1;
		y = y_1;
		xend = x_2;

		y2 = y_2;
		x2 = x_2;

		drawPixel(x, y, x2, y2);

		if (indexChanged)
			return;

		// ------------Capture interlude----------------
		if (paused.equals(Boolean.FALSE))
			clickSound.play();
		try {
			Thread.sleep(PAUSE);
		} catch (InterruptedException e) {
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}
		// ---------------------------------------------

		if (x < xend) {
			while (x < xend && !(indexChanged)) {
				x++;
				if (p < 0)
					p += twody;
				else {
					y--;
					p += twodydx;
				}
				drawPixel(x, y, x2, y2);
			}
		} else {
			/**
			 * Here we are going from a higher x1, to a lower x value, and then
			 * a lower y value to a higher one.
			 * 
			 * x1 > x2 && y1 < y2
			 * 
			 */
			while (y < yend && !(indexChanged)) {
				x--;
				if (p < 0)
					p += twody;
				else {
					y++;
					p += twodydx;
				}
				drawPixel(x, y, x2, y2);
			}
		}
	}

	public void lineNegSteep(int x_1, int y_1, int x_2, int y_2) {
		int dx, dy, p, twodx, twodxdy, x, y, x2, y2, yend, xend;

		dx = Math.abs(x_2 - x_1);
		dy = Math.abs(y_2 - y_1);
		twodx = 2 * dx;
		twodxdy = 2 * (dx - dy);

		p = 2 * dy - dx;
//		p = twodx - dy;
		x = x_1;
		y = y_1;

		x2 = x_2;
		y2 = y_2;

		yend = y2;
		xend = x_2;

		drawPixel(x, y, x2, y2);

		if (indexChanged)
			return;
		// ------------Capture interlude----------------
		if (paused.equals(Boolean.FALSE))
			clickSound.play();
		try {
			Thread.sleep(PAUSE);
		} catch (InterruptedException e) {
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}
		// ---------------------------------------------

		/**
		 * (x1 > x2) AND (y1 < y2)
		 */

		if (x > xend && y < yend) {
			while (y < yend && !(indexChanged)) {
				y++;
				if (p < 0)
					p += twodx;
				else {
					x--;
					p += twodxdy;
				}
				drawPixel(x, y, x2, y2);
			}

		} else {
			while (y > yend && !(indexChanged)) {
				y--;
				if (p < 0)
					p += twodx;
				else {
					x++;
					p += twodxdy;
				}
				drawPixel(x, y, x2, y2);
			}
		}
	}

	private void drawPixel(int x, int y, int x2, int y2) {
		drawX = x+X_OFFSET;
		drawY = y+Y_OFFSET;

		synchronized (animateThread) {
			while (paused.equals(Boolean.TRUE)) {
				try {
					animateThread.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
					Thread.currentThread().interrupt();
				}
			}
		}

		if (indexChanged)
			return;

		display.syncExec(new Runnable() {
			public void run() {
				animationCanvas.redraw();
			}
		});

		if (indexChanged)
			return;

		// Animation delay
		try {
			Thread.sleep(DELAY);
		} catch (InterruptedException e) {
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}

	}

	/**
	 * <p>
	 * Creates the toolbar using the specified
	 * {@link org.eclipse.swt.widgets.Composite#Composite} as the parent.
	 * 
	 * <p>
	 * No actions will be associated with the toolbar object. It is a container
	 * for the toolitems.
	 * </p>
	 * 
	 * @param the
	 *            <code>parent</code> <code>Composite</code> to which the
	 *            toolbar object will be added
	 */
	private void createToolbar(final Composite parent) {
		// create the top,right justified, flat toolbar
		toolBar = new ToolBar(parent, SWT.TOP | SWT.FLAT);

		// set toolbar layout data to fill up horizontal row of its location.
		toolBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		String contextKeyPrefix = VIEW_ID + ".AnimationCanvas.ToolBar";
		
		PrismPlugin.getDefault().addWidget(contextKeyPrefix, toolBar);
		
		backward = new ToolItem(toolBar, SWT.PUSH);
		backward.setImage(PrismPlugin.getImageDescriptor("icons/step_back.gif")
				.createImage());

		PrismPlugin.getDefault().addWidget(contextKeyPrefix + ".backward", backward);
		
		forward = new ToolItem(toolBar, SWT.PUSH);
		forward.setImage(PrismPlugin.getImageDescriptor("icons/step.gif")
				.createImage());
		
		PrismPlugin.getDefault().addWidget(contextKeyPrefix + ".forward", forward);

		play = new ToolItem(toolBar, SWT.CHECK);
		play.setImage(PrismPlugin.getImageDescriptor("icons/start.gif")
				.createImage());
		PrismPlugin.getDefault().addWidget(contextKeyPrefix + ".play", play);
		
		pause = new ToolItem(toolBar, SWT.PUSH);
		pause.setImage(PrismPlugin.getImageDescriptor("icons/pause.gif")
				.createImage());
		PrismPlugin.getDefault().addWidget(contextKeyPrefix + ".pause", pause);

		stop = new ToolItem(toolBar, SWT.PUSH);
		stop.setImage(PrismPlugin.getImageDescriptor("icons/stop.gif")
				.createImage());
		stop.setEnabled(false);
		PrismPlugin.getDefault().addWidget(contextKeyPrefix + ".stop", stop);

		forward.addSelectionListener(new SelectionAdapter() {
		});

		backward.addSelectionListener(new SelectionAdapter() {
		});

		reload = new ToolItem(toolBar, SWT.PUSH);
		reload.setImage(PrismPlugin.getImageDescriptor("icons/refresh.gif")
				.createImage());

		reload.addSelectionListener(new SelectionAdapter() {
		});

		PrismPlugin.getDefault().addWidget(contextKeyPrefix + ".reload", reload);
		
		stop.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(final SelectionEvent e) {
				synchronized (animateThread) {
					play.setSelection(false);
					stop.setEnabled(false);
					paused = Boolean.TRUE;
					animateThread.notifyAll();
				}
			}

			public void widgetDefaultSelected(final SelectionEvent e) {
				widgetSelected(e);
			}

		});

		play.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(final SelectionEvent e) {
				if (null == animateThread
						|| !animateThread.getState().equals(
								Thread.State.WAITING)) {
					stop.setEnabled(true);
					paused = Boolean.FALSE;
					startAnimation();
				} else {
					synchronized (animateThread) {
						stop.setEnabled(true);
						paused = Boolean.FALSE;

						animateThread.notifyAll();
					}
				}
			}

			public void widgetDefaultSelected(final SelectionEvent e) {
				widgetSelected(e);
			}
		});
		
	}

	public void dispose() {
		animationCanvas.dispose();

		if (null != animateThread) {
			animateThread = null;
			Thread.currentThread().interrupt();
		}

		if (offScreenImage != null && !offScreenImage.isDisposed())
			offScreenImage.dispose();

		if (pointer != null && !pointer.isDisposed())
			pointer.dispose();

		clickSound.stop();
		super.dispose();
	}

}
